---
title: 初识 Web3
date: 2023-07-14 20:10:43
images: ['https://img1.baidu.com/it/u=3534547064,2452802363&fm=253&fmt=auto&app=138&f=JPEG?w=801&h=500']
summary: 'Web3作为下一代互联网的核心架构，正在通过区块链、智能合约、分布式存储等技术重塑数字世界的信任与协作模式。虽然不知道是否又是一个“元宇宙”，但是不影响我们去探讨学习'
tags: 
 - web
---

## Web3

### NFT(Non-fungible token)

NFT是一种非同质化代币，每个NFT都是独一无二的，具有独特的属性和所有权。NFT可以代表任何数字资产，如艺术品、音乐、游戏道具等。NFT的不可替代性使得它们在交易和收藏中具有很高的价值。
- 土地NFT：未来想象类
- 虚拟资产NFT：游戏类
- 数字艺术品NFT：艺术类（表情版权）
- 门票NFT：活动类
- 版权NFT：知识产权类
- 交易NFT：金融类

> ERC-721 和 ERC-1155

ERC-721 和 ERC-1155 是以太坊上两种不同的非同质化代币（NFT）标准。它们之间的主要区别在于它们如何处理通证的数量和所有权。

| 特性 | ERC-721 | ERC-1155 |
| --- | --- | --- |
| 同质化 | 非同质化 | 同质化&非同质化|
| 批次处理 | 每次处理一个通证 | 每次处理多个通证 |
|  |  |  |


### 区块链（Blockchain）
特征：
- 去中心化：对等网络每个节点即使服务器又是客户端
- 共识机制：PoW 和 PoS
  - PoW：工作量证明机制，比特币机制，比较消耗资源，但是比较有效
  - PoS：权益证明机制
  - POA:权威证明机制
  - POC:容量证明机制
  - CPOC:有条件的容量证明机制
- 不可篡改：修改数据会影响整个链的其他数据

应用场景：
- 零售：供应链管理、防伪溯源
- 众筹：资金筹集、股权分配（资金使用）
- 物联网：设备管理、数据共享
- 数字货币：比特币、以太坊等
- 供应链管理：追踪商品从生产到消费的全过程
- 医疗健康：记录患者的医疗记录和健康数据（医院数据不互通）
- 版权保护：记录数字资产的所有权和交易记录
- 金融科技：去中心化交易所、借贷平台等
- 保险：记录保险理赔和赔付情况
- 能源：记录能源生产和消费的记录
- 媒体：记录版权和交易记录 

类型：
- 公有链：任何人都可以参与和访问，如比特币、以太坊
- 私有链：只有授权用户可以参与和访问，如企业内部应用
- 联盟链：多个组织共同维护和访问，如金融行业联盟
- 混合链：结合公有链和私有链的特点，如混合型交易所

类型对比
| 类型 | 公有链 | 私有链 | 联盟链 | 混合链 |
| --- | --- | --- | --- | --- |
| 访问权限 | 公开 | 私有 | 联盟 | 混合 |
| 写入制 | 公开 | 获批参与者 | 获批参与者 | 混合 |
| 所属者 | 无 | 单一实体 | 多方实体 | 混合 |
| 匿名性 | 是 | 否 | 否 | 否 |
| 共识机制 | PoW、PoS | 权限控制 | 共识算法 | 混合 |
| 交易速度 | 慢 | 快 | 快 | 混合 |
|  |  |  |  |  |

hash算法
- 从hash值不可以反向推导出原始的数据
- 输入数据的微小变化会得到完全不同的hash值
- 相同的数据会得到相同的值
- 执行效率要高效，长的文本也能快速地计算出哈希值
- hash算法的冲突概率要小

### DApp（去中心化应用）


### 智能合约

web3.eth:用于与以太坊区块链和智能合约之间的交互
web3.utils:包含一些辅助方法
web3.shh:用于协议进行通信的P2P和广播。
web3.bzz:用于与群网络交互的Bzz模块。
github地址:https://github.com/web3/web3.js/tree/v1.0.0-beta.34
web3.js开发文档:https://web3js.readthedocs.io/en/v1.8.1/
web3.js 中文文档: https://learnblockchain.cn/docs/web3.js/

## web端技术开发

### 账户创建

```js
import Web3 from 'web3'
// 创建账户
const account = Web3.eth.accounts.create();
// create() 参数它是一个可选项，是一个随机字符串，将作为解锁账号的密码。如果没有传递字符串，则使
// 用random生成随机字符串。
console.log(account);
```
![账户](https://kodo.huixiangwuyou.com/blog/images/web/2250526230325.png)

### 获取账户余额

```js
// 获取账户余额
const balance = await Web3.eth.getBalance(account.address);
console.log(Web3.utils.fromWei(balance, 'ether'));
```

![转账](https://kodo.huixiangwuyou.com/blog/images/web/20250526232520.png)
### 发送交易

> npm install ethereumjs-tx@1.3.7  

```js

import Web3 from 'web3'
import Tx from 'ethereumjs-tx'
// import { ethers } from 'ethers'
// import { keccak256 } from 'ethers/lib/utils'
// import { Buffer } from 'buffer'
// import { keccak256 as keccak256_ } from 'keccak256'
// import { Wallet } from 'ethereumjs-wallet'
// 获取 gasprice
const gasPrice = await Web3.eth.getGasPrice();
// 发送交易
const tx = {
  from: account.address, // 转账账户
  to: '0x1234567890abcdef1234567890abcdef12345678',  // 接收账户
  value: Web3.utils.toWei('1', 'ether'),  // 转账金额  需要单位转化
  // gas: 21000, // 转账需要消耗的gas   web3.utils.toWei('1', 'gwei'),
  gasPrice: Web3.utils.toWei('10', 'gwei'), // gas价格
};
// 方式一：
// 使用私钥签名交易: privateKey
const signedTx = await Web3.eth.accounts.signTransaction(tx, privateKey);
const receipt = await Web3.eth.sendSignedTransaction(signedTx.rawTransaction);
console.log(receipt);


// 方式二：
// 算出 gas
tx.gas = await Web3.eth.estimateGas(tx);
let t = new Tx(tx);
t.sign(privateKey)
const signedTx = '0x' + t.serialize().toString('hex');
const trans = Web3.eth.sendSignedTransaction(signedTx);


trans.on('transactionHash', function(txid){
  console.log("交易id",txid);
  console.log("https://goerli.etherscan.io/tx/${txid}");
});
trans.on('receipt', function(receipt){
  console.log("交易节点",receipt);
});
trans.on('confirmation', function(confirmationNumber, receipt){
  console.log("交易确认",confirmationNumber ,receipt);
});
trans.on('error', function(error){
  console.log("交易失败",error);
});

```

### 助记词创建账户

需要使用 bip39 协议将助记词转换成种子，再通过 ethereumjs-wallet 库生成hd钱包，根据路径的不同从hd钱包中获取不同的keypair，keypair中就包含有公钥、私钥，再通过ethereumjs-util 库将公钥生成地址，从而根据助记词获取所有关联的账号，能获取到公钥、私钥、地址等数据信息。

> npm install bip39  ethereumjs-util ethereumjs-wallet

```js
import Web3 from 'web3'
import {
  generateMnemonic,
  mnemonicToSeed,
  validateMnemonic,
  fromEntropy,
} from 'bip39'
import {hdkey} from 'ethereumjs-wallet'
// 根据不同情况安装， vite 就安装vite-plugin-node-polyfills插件，然后在vite.config.js中配置
// 注意：vite-plugin-node-polyfills 0.9.0之后的版本有不兼容问题
import { Buffer } from 'buffer'

// 生成助记词  floor upon canvas income hurt abuse gather bean august hold coffee heavy
const mnemonic = generateMnemonic();

const seed = mnemonicToSeedSync(mnemonic);
// 生成种子
const hdwallet = hdkey.fromMasterSeed(seed);
// 生成密钥对
let keypair = hdwallet.derivePath(`m/44'/60'/0'/0/0`);
// 获取钱包对象
let wallet = keypair.getWallet();
// 获取钱包地址
const address = wallet.getAddressString();
// 获取钱包校验地址   全部转小写后与钱包地址相同
const localAddress = wallet.getChecksumAddressString();
//获取私钥
const privateKey = wallet.getPrivateKeyString();

console.log(address, privateKey);
```
> derivePath相关

```js
// m/44'/60'/0'/0/0
// m: 表示主私钥
// 44': 表示BIP44协议
// 60': 表示以太坊
// 0': 表示第一个账户
// 0: 表示第一个子账户
// 0: 表示第一个密钥对
```

BIP44 是在 BIP32 和 BIP43 的基础上增加多币种，提出的层次结构非常全面，"它允许处理多个币种，多个帐户，每个帐户有数百万个地址。
在BIP32路径中定义以下5个级别:
```
m/purpse'/coin_type'/account'/change/address_index
```
- purpse:在BIP43之后建议将常数设置为44'。表示根据BIP44规范使用该节点的子树。
- Coin_type:币种，表一个主节点(种子)可用于无限数量的独立加密币，如比特币，Litecoin或Namecoin。此级别为每个加密币创建一个单独的子树，避免重用已经在其它链上存在的地址。开发人员可以为他们的项目注册未使用的号码。
- Account:账户，此级别为了设置独立的用户身份可以将所有币种放在一个的帐户中，从0开始按顺序递增
- Change:常量0用于外部链，常量1用于内部链，外部链用于钱包在外部用于接收和付款。内部链用于在钱包外部不可见的地址，如返回交易变更。
- Address_index:地址索引，按顺序递增的方式从索引0开始编号
BIP44的规则使得 HD钱包非常强大，用户只需要保存一个种子，就能控制所有币种，所有账户的钱包，因此由BIP39 生成的助记词非常重要，所以一定安全妥善保管，那么会不会被破解呢?如果一个 HD 钱包助记词是 12 个单词，一共有 2048 个单词可能性，那么随机的生成的助记词所有可能性大概是 5e+39，因此几乎不可能被破解。


### 导出账户(keystore)

```js
import Web3 from 'web3'
import ethwallet, { Wallet } from 'ethereumjs-wallet'

// 导出账户
// web3方式导出  必须输入正确的密码
web3.eth.accounts.encrypt(privateKey, "123456").then((result) => {
  console.log("🚀 ~ result:", result);
 // 反向获取私钥  必须输入正确的密码
  web3.eth.accounts.decrypt(result, "123456").then((decrypted) => {
    console.log("🚀 ~ decrypted:", decrypted.privateKey);
  })

});


// wallet 钱包方式导出  必须输入正确的密码
wallet.toV3("123456").then((keystore) => {
  console.log("🚀 ~ keystore:", keystore);
  // 反向获取私钥  必须输入正确的密码
  ethwallet.fromV3(keystore, "123456").then((wallet) => {
    let a = decrypted.getPrivateKeyString()
    console.log("🚀 ~ privateKey:", a);
  })
});
```
![keystore](https://kodo.huixiangwuyou.com/blog/images/web/20250527181146.png)


## solidity|Remix
[solidity](https://solidity-cn.readthedocs.io/zh/develop/)、[solidity示例(科学上网)](https://solidity-by-example.org)
[Remix官网](https://remix.ethereum.org/)


### solidity数据类型

- 值类型
  - bool：布尔类型，true或false
  - int/unit：整形  unit8、unit16、unit32、unit64、unit128、unit256、int8、int16、int32、int64、int128、int256
    - 默认为uint256
    - int:有符号整形
      - int8:有符号整形，占8位，范围-128~127
      - int16:有符号整形，占16位，范围-32768~32767
      - int32:有符号整形，占32位，范围-2147483648~2147483647
      - int64:有符号整形，占64位，范围-9223372036854775808~9223372036854775807
      - int128:有符号整形，占128位，范围-170141183460469231731687303715884105728~170141183460469231731687303715884105727
      - int256:有符号整形，占256位，范围-57896044618658097711785492504343953926634992332820282019728792003956564819968~57896044618658097711785492504343953926634992332820282019728792003956564819967
    - uint：无符号整形
      - uint8:无符号整形，占8位，范围0~255
      - uint16:无符号整形，占16位，范围0~65535
      - uint32:无符号整形，占32位，范围0~4294967295
      - uint64:无符号整形，占64位，范围0~18446744073709551615
  - address：地址类型
  - 定长字节数据
  - fixed/ufixed：定长浮点
  - enum：枚举类型
  - function：函数类型
- 引用类型
  - array：数组类型，
    - 不定长字符串数据 bytes
    - 固定长度字符串数据  bytes1~bytes32 因为是固定长度，所以比bytes更节省gas
  - struct：结构体类型
  - mapping：映射类型,与数组个结构体一样，映射也是引用类型，但是映射是一对一的键值对关系
    - mapping(keyType => valueType)： keyType可以是任何内置类型，不允许使用引用类型和复杂结构， valueType可以是任何类型


```sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

contract DataTypeDemo {
  unit public x = 1;
  unit8 public z = 1;
  address public addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
  address public add = msg.sender;
  struct Person {
    string name;
    unit age;
  }
  Person person;

  // 定长数组   不可以直接修改数组长度 fixedArray.length = 4;
  unit[3] public fixedArray = [1, 2, 3];

  function sumArray() public view returns (unit) {
    unit sum = 0;
    for (unit i = 0; i < fixedArray.length; i++) {
      sum += fixedArray[i];
    }
    return sum;
  }

}
```

>  string 类型是用于存储 UTF-8 编码文本的动态长度数据类型

- 动态大小：长度不固定，可以在运行时改变
- UTF-8 编码：支持多字节字符（如中文、表情符号等）
- 引用类型：存储在 storage 中时是引用类型
- 存储方式：类似于 bytes 类型，但有专门的字符串处理功能
  ```sol
  string memory myString = "Hello, 世界!";
  // 直接 myString.length 语法在 Solidity 中不合法
  uint length = bytes(myString).length; // 转换为 bytes 后获取长度
  ```


### solidity修饰符
- view：只读方法，不会修改状态变量，不会消耗gas
- pure：纯方法，不会修改状态变量，不会消耗gas，不会访问状态变量
- payable：允许接收以太币
- public：可以被合约内部和外部访问
- external：只能被合约外部访问,只有其他合约或者账户可以调用，定义该函数的合约不能调用除非使用`this`关键字
- internal：只能被合约内部访问，类似Java的protected


```sol
pragma solidity ^0.8.7;

contract HelloWorld {
  // 定义一个状态变量
  string public name = "Hello, World!";

  // 构造函数
  constructor() {}
  // 不修改变量
  function sayName() public view returns (string memory) {
    return name;
  }

  function setName(string calldata _name) public  {
      name = _name;
  }
}
```

### 变量
分为局部变量、状态变量和全局变量，局部变量在函数内部定义，状态变量是合约内部定义的变量，可以存储在区块链上，而局部变量是函数内部定义的变量，无法存储在区块链上。全局变量在函数外部定义，全局变量在函数内部也可以使用。

| 全称 | 返回 |
| --- | --- |
| blockhash(uint blockNumber) returns (bytes32) | 给定区块的哈希值-只适用于256最近区块,不包含当前区块。 |
| block.coinbase (address payable) | 当前区块矿工的地址 |
| block.difficulty (uint) | 当前区块的难度 |
| block.gaslimit (uint) | 当前区块的gaslimit |
| block.number (uint) | 当前区块的number |
| block.timestamp (uint) | 当前区块的时间戳，为unix纪元以来的秒 |
| gasleft() returns (uint256) | 剩余 gas |
| msg.data (bytes calldata) | 完成 calldata |
| msg.sender (address payable) | 消息发送者(当前 caller) |
| msg.sig (bytes4) | kalldata的前四个字节(function identifier) |
| msg.value (uint) | 当前消息的wei值 |
| now (uint) | 当前块的时间戳 |
| tx.gasprice (uint) | 交易的gas价格 |
| tx.origin (address payable) | 交易的发送方 |
|  |  |


#### 变量的存储位置
- storage：存储在区块链上的变量，会永久保存，占用合约的存储空间，消耗gas
  - 存储中的数据是永久保存，存储的是KeyValue库
  - 存储中的数据写入区块链，因此会修改状态，造成存储使用成本高的原因
  - 占用一个256位的槽需要消耗20,000 gas，修改一个256位的槽需要消耗5,000 gas
  - 当清零一个槽时，会返回一定数量的gas
- memory：存储在内存中的变量，不会永久保存，不会占用合约的存储空间，不会消耗gas
  - 数据仅在函数执行期间存在，执行完毕后被销毁
  - 读写一个内存槽会消耗 3gas
  - 为避免矿工工作量大，22个操作后的单操作成本会上涨
- calldata：存储在内存中的变量，不会永久保存，不会占用合约的存储空间，不会消耗gas，用于函数参数
  - 只能用于函数声明的参数，是不可变的
  - 是临时的，函数执行后销毁，是最便宜的存储位置
  - 不能作为返回值，不能用于状态变量
- stack：存储在栈中的变量，智能合约免费提供，不会永久保存，不会占用合约的存储空间，不会消耗gas但是数量有限。

```sol
pragma solidity ^0.8.7;

contract Demo {
  // 定义一个storage变量
  unit x = 1;
  // 错误定义storage变量:   unit storage x = 1;

  // 断言不成立，不修改变量
  function doAssert() public  returns (unit) {
    assert(x > 1);
    x = 2;
    return x;
  }
  // require不成立，不修改变量
  function doRequire() public  returns (unit) {
    require(x > 1, "x is not equal to 1");
    x = 2;
    return x;
  }
}
```


#### 变量引入与继承

```sol
// 1.直接引入项目文件下的合约
// 2.引入GitHub上的合约
// 3.通过包引入
import  "./Factor.sol";
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
import { helloWorld } from "./HelloWorld.sol";

// 继承合约
contract HelloWorldChild is Factor {
  // 定义变量
  helloWorld[] public helloWorlds;
  // 构造函数  调用合约的方法和输入简称
  constructor() Factor("functionDo","ft") {}
}

```


### 错误处理
- require：用于检查条件，如果条件不满足，则抛出异常，并回滚所有状态更改。
- revert：用于抛出异常，并回滚所有状态更改。
- assert：用于检查条件，如果条件不满足，则抛出异常，并回滚所有状态更改。

```sol
pragma solidity ^0.8.7;

contract ErrorDemo {
  // 定义一个状态变量
  unit public x = 1;
  // 断言不成立，不修改变量
  function doAssert() public  returns (unit) {
    assert(x > 1);
    x = 2;
    return x;
  }
  // require不成立，不修改变量
  function doRequire() public  returns (unit) {
    require(x > 1, "x is not equal to 1");
    x = 2;
    return x;
  }
}
``` 

### 事件
事件是合约和用户之间的通信方式，用户可以通过事件来获取合约的状态变化，事件在合约中定义，用户可以通过web3.js来监听事件。

```sol
pragma solidity ^0.8.7;

contract EventDemo {
  event Log(unit);
  event Log(string);
  event Log(address);
  event Log(unit, string, address);

  function doEvent() public {
   // 打印数字
    emit Log(1);
    // 打印字符串
    emit Log("hello");
    // sender就是address
    emit Log(msg.sender);
  }
}

```

### modifier
修饰器是合约中的一种特殊函数，用于修改函数的行为，修饰器可以用于函数的执行前、执行后、执行中、执行失败等场景。

```sol
pragma solidity ^0.8.7;

contract ModifierDemo {
  // 定义一个状态变量
  unit public x = 1;
  // 定义一个修饰器
  modifier checkX() {
    require(x > 1, "x is not equal to 1");
    _;
  }
  // 使用修饰器
  function doCheckX() public checkX {
    x = 2;
  }
}
```

### 虚函数
虚函数是合约中的一种特殊函数，用于定义合约的接口，子合约可以重写虚函数，实现自己的逻辑。

```sol
pragma solidity ^0.8.7;

contract Factor {
  // 定义一个虚函数
  function factor(unit n) public virtual returns (unit) {
    return n;
  }
}

contract HelloWorld is Factor {
  // 重写虚函数 上面的合约已经默认方法了，可以不需要重写
  function factor(unit n) public override returns (unit) {
    return n * 2;
  }
}
```


### 合约创建

```sol
// 智能合约的许可协议
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

contract HelloWorld {
    // 定义一个状态变量
    string public name = "Hello, World!";

    // 添加 payable 构造函数
    constructor() payable {}

    function sayName() public view returns (string memory) {
        return name;
    }

    function setName(string calldata _name) public  {
        name = _name;
    }
}
```
创建合约后选择编译的版本，尽量不要选当前最新的，因为低版本可以被高版本编译，但是高版本不兼容低版本编译，所以选择一个比较稳定的版本，然后点击创建合约，会自动生成合约地址。
![创建合约](https://kodo.huixiangwuyou.com/blog/images/web/20250527233628.png)


### 部署合约
选择合适的环境和选择一个账户，然后根据编译的合约后点击deploy，就可以看到合约定义的变量和方法了
![部署合约](https://kodo.huixiangwuyou.com/blog/images/web/20250527234743.png)

> 若是要部署到以太坊线上，要在Remix的deployed选择一个线上方式，在点击deploy，然后在deployed contract中就可以看到合约地址了

#### 部署到线上

若是没有引用文件则直接复制合约地址，然后打开https://sepolia.etherscan.io/，在search中输入合约地址，然后点击search，就可以看到合约的详细信息了

若是引入了文件，在文件目录中右键文件，选择`Flatten`将文件平铺，然后选择新的合约文件，将新文件内容的合约软件协议删除多余的，然后执行下面的操作

![线上搜索地址](https://kodo.huixiangwuyou.com/blog/images/web/20250601214105.png)
![发布合约操作](https://kodo.huixiangwuyou.com/blog/images/web/20250601214556.png)
![合约设置操作](https://kodo.huixiangwuyou.com/blog/images/web/20250601214705.png)
![合约代码发布](https://kodo.huixiangwuyou.com/blog/images/web/20250601214822.png)




## Chainlink

[Chainlink](https://docs.chain.link/) 作为其主要的去中心化预言机网络（Decentralized Oracle Network, DON），为智能合约提供安全、可靠的链下数据接入。
主要解决了智能合约与外部数据源之间的信任问题，使得智能合约能够访问到真实世界的数据，从而实现更加智能化的应用。例如，智能合约可以基于天气数据、股票价格、加密货币价格等`实时数据`来执行交易、调整策略等操作。



## ERC
ERC-20: 标准代币协议
ERC-721: 非同质化代币协议
ERC-1155: 多代币协议

[ERC-20官网](https://wizard.openzeppelin.com/) 设置自己的代币。

![设置自己的代币](https://kodo.huixiangwuyou.com/blog/images/web/20250529222658.png)


将自己的代币代码放到Remix编译执行，然后点击deploy，就可以看到合约地址了

### 调用合约方法

```js
// 判断是否连接了"MetaMask"插件
if (typeof window.ethereum !== 'undefined') {
  // 连接到以太坊网络
  const web3 = new Web3(window.ethereum);
  // 请求用户授权连接
  await window.ethereum.enable();
  // 获取账户
  const accounts = await web3.eth.getAccounts();
  // 调用合约方法  contractAddress 为之前Remix线上部署的地址
  const contractInstance = new web3.eth.Contract(abi, contractAddress);
  // 调用合约方法
  const result = await contractInstance.methods.myMethod().call();
  console.log(result);
} else {
  console.error('MetaMask is not installed');
}
// 连接到以太坊网络
const web3 = new Web3('https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID');

// 合约ABI
const abi = [
  // 合约方法定义
  // ...
];

// 合约地址
const contractAddress = '0x1234567890abcdef1234567890abcdef12345678';

// 获取账户
// 调用合约方法
const contractInstance = new web3.eth.Contract(abi, contractAddress);
const result = await contractInstance.methods.myMethod().call();
console.log(result);
```



## web3相关网站

[以太坊官网](https://ethereum.org/zh/)

### 挖矿地址
https://www.tokenpocket.pro/
https://goerli-faucet.pk910.de/










